﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace WCamera
{
	public class Camera
	{
		#region Var
		protected Matrix matView;
		protected Matrix matProj;
		protected Viewport viewPort;

		protected Vector3 vPosition;
		protected Vector3 vUp;
		protected Vector3 vDirection;
		protected BoundingFrustum frustum;
		#endregion

		#region Readonly Properties

		public Matrix MatView { get { return matView; } }

		public Matrix MatProj { get { return matProj; } }

		public Matrix MatViewProj { get { return matView * matProj; } }

		public Vector3 Position { get { return vPosition; } }

		public Vector3 Right { get { return Vector3.Cross(vDirection, vUp); } }

		public Vector3 Up { get { return vUp; } }

		public Vector3 Direction { get { return vDirection; } }

		public Viewport ViewPort { get { return viewPort; } set { viewPort = value; Update(); } }

		public BoundingFrustum Frustum { get { return frustum; } }

		#endregion

		public Camera()
		{
			Reset();
			viewPort.X = viewPort.Y = 0;
			viewPort.Width = viewPort.Height = 100;
			viewPort.MaxDepth = 1;
			viewPort.MinDepth = 0;
			frustum = new BoundingFrustum(Matrix.Identity);
			Update();
		}

		public void Update()
		{
			const float fov = MathHelper.Pi * 53 / 180;
			matView = Matrix.CreateLookAt(vPosition, vPosition + vDirection, vUp);
			matProj = Matrix.CreatePerspectiveFieldOfView(fov, viewPort.AspectRatio, 1.0f, 10000.0f);
			frustum.Matrix = MatViewProj;
		}

		public void SetPosition(Vector3 pos)
		{
			if (float.IsNaN(pos.X) || float.IsNaN(pos.Y) || float.IsNaN(pos.Z)
				|| float.IsInfinity(pos.X) || float.IsInfinity(pos.Y) || float.IsInfinity(pos.Z))
			{
				return;
			}

			vPosition = pos;
			Update();
		}

		public void SetRotation(Vector3 rot)
		{
			if (float.IsNaN(rot.X) || float.IsNaN(rot.Y) || float.IsNaN(rot.Z)
				|| float.IsInfinity(rot.X) || float.IsInfinity(rot.Y) || float.IsInfinity(rot.Z))
			{
				return;
			}

			Matrix rotMat = Matrix.CreateFromYawPitchRoll(rot.Y, rot.X, rot.Z);
			Vector3 tempDir = Vector3.TransformNormal(new Vector3(0, 0, 1), rotMat);
			Vector3 tempUp = Vector3.TransformNormal(new Vector3(0, 1, 0), rotMat);
			vDirection = tempDir;
			vUp = tempUp;
			Update();
		}

		public void SetDirection(Vector3 dir)
		{
			dir = Vector3.Normalize(dir);
			vDirection = dir;
			vUp = Vector3.Cross(Vector3.Cross(dir, new Vector3(0, 1, 0)), dir);
			Update();
		}

		public void RotateAxisAngle(Vector3 axis, float angle)
		{
			Matrix matRot = Matrix.CreateFromAxisAngle(axis, angle);
			Vector3 tempDir = Vector3.TransformNormal(vDirection, matRot);
			Vector3 tempUp = Vector3.TransformNormal(vUp, matRot);

			if (float.IsNaN(tempDir.X) || float.IsNaN(tempDir.Y) || float.IsNaN(tempDir.Z)
				|| float.IsNaN(tempUp.X) || float.IsNaN(tempUp.Y) || float.IsNaN(tempUp.Z))
			{
				return;
			}

			vDirection = tempDir;
			vUp = tempUp;
			Update();
		}

		public void RotateAxisAngleCenter(Vector3 axis, float angle, Vector3 center)
		{
			Matrix matRot = Matrix.CreateFromAxisAngle(axis, angle);
			Vector3 vTrans = center - vPosition;
			Vector3 tempDir = Vector3.TransformNormal(vDirection, matRot);
			Vector3 tempUp = Vector3.TransformNormal(vUp, matRot);
			Vector3 vTransNormal = Vector3.Normalize(vTrans);
			Vector3 vTransNormalRot = Vector3.TransformNormal(vTransNormal, matRot);
			vTransNormalRot *= vTrans.Length();

			if (float.IsNaN(tempDir.X) || float.IsNaN(tempDir.Y) || float.IsNaN(tempDir.Z)
				|| float.IsNaN(tempUp.X) || float.IsNaN(tempUp.Y) || float.IsNaN(tempUp.Z)
				|| float.IsNaN(vTransNormalRot.X) || float.IsNaN(vTransNormalRot.Y) || float.IsNaN(vTransNormalRot.Z))
			{
				return;
			}

			vDirection = tempDir;
			vUp = tempUp;
			SetPosition(center - vTransNormalRot);
		}

		public void FocusOnBoundingBox(BoundingBox bb)
		{
			float tanHalfAngle = (float)Math.Tan(MathHelper.Pi * 53 / 360);

			Vector3 center = (bb.Max + bb.Min) * 0.5f;
			Vector3 frontCenter = new Vector3(center.X, center.Y, bb.Min.Z);
			float bbHalfX = (center.X - bb.Min.X) * 1.2f;
			float zbackward = bbHalfX / tanHalfAngle;
			SetPosition(new Vector3(center.X, center.Y, center.Z - zbackward));
		}

		public void Project(Vector3[] srcArray, out Vector2[] destArray)
		{
			destArray = new Vector2[srcArray.Length];
			for (int i = 0; i < srcArray.Length; i++)
			{
				Vector3 temp = viewPort.Project(srcArray[i], matProj, matView, Matrix.Identity);
				destArray[i] = new Vector2(temp.X, temp.Y);
			}
		}

		public void Reset()
		{
			vPosition = new Vector3(0, 0, -1);
			vUp = new Vector3(0, 1, 0);
			vDirection = new Vector3(0, 0, 1);
		}
	}
}